﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Netfraction.Network.Protocol.BASE.Handler
{
    public class HandlerINF : ProtocolMessageActionHandler
    {

        public HandlerINF() : base()
        {
            mParameterCodes = new List<string>(new string[]
            {"ID", "PD", "I4", "I6", "U4", "U6", "SS", "SF", "VE", "US", "DS", "SL", "AS", 
                "AM", "EM", "NI", "DE", "HN", "HR", "HO", "TO", "CT", "AW", "SU", "RF"});

            mSupportedConnectionStates = new List<RemoteMachine.ConnectionState>();
            mSupportedConnectionStates.Add(RemoteMachine.ConnectionState.Identify);
            mSupportedConnectionStates.Add(RemoteMachine.ConnectionState.Normal);
        }

        #region ProtocolMessageActionHandler Members

        public override string ProtocolActionCode
        {
            get { return "INF"; }
        }

        public override bool HandleProtocolMessage(RemoteMachine.Node node, ProtocolMessage message, IEnumerable<ProtocolFeature> extenders)
        {
            if (!IsSupportedConnectionState(node))
                return false;

            if (message.Parameters.Count == 0)
                return false;

            message.Parameters = DecodeParameters(message.Parameters);

            RemoteMachine.Node infNode = null;
            // check if the INF message is about the hub itself
            bool notHubIdentity = true;
            var a = message.Parameters.Where(c => c.ParameterCode == "CT");
            if (a.Count() > 0)
            {
                if ((IdentityNodeTypes)Convert.ToInt32(a.First().ParameterValue) == IdentityNodeTypes.Hub)
                    notHubIdentity = false;
            }

            // if not about the hub then check if the client knows about this peer, otherwise add a new peer
            LocalMachine.Client client = LocalMachine.Client.GetInstance();
            if (notHubIdentity)
            {
                var byClientID = message.Parameters.Where(c => c.ParameterCode == "ID");
                if (byClientID.Count() > 0)
                {
                    string clientID = byClientID.First().ParameterValue;
                    if (!client.Peers.Contains(clientID))
                    {
                        RemoteMachine.Peer peer = new RemoteMachine.Peer();
                        peer.SessionId = message.SessionId;
                        peer.IsOnline = true;
                        peer.Identity.ClientId = clientID;
                        var nick = message.Parameters.Where(c => c.ParameterCode == "NI");
                        if (nick.Count() > 0)
                        {
                            peer.Identity.Nickname = nick.First().ParameterValue;
                            //Console.WriteLine("*** Joins: {0}", nick.First().ParameterValue);
                        }
                        client.Peers.Add(peer);
                    }
                    else if (client.Peers[clientID].SessionId != message.SessionId & message.MessageType != ProtocolMessage.MessageTypeEnum.Client)
                    {
                        // if we let this happen in a CINF we will set the SessionId to null
                        // occurs when peer reconnects, without exiting their client
                        // probably not gonna happen now that I'm removing offline Peers from the Peer list
                        client.Peers[clientID].SessionId = message.SessionId;
                        client.Peers[clientID].IsOnline = true;
                        /*var nick = message.GETParameterOptions.Where(mC => mC.ParameterCode == "NI");
                        if (nick.Count() > 0)
                        {
                            Console.WriteLine("****************************************** Joins: {0}", nick.First().ParameterValue);
                        }*/
                    }
                    infNode = client.Peers[clientID];
                }
                else // try looking by SessionId instead
                {
                    var bySessionID = client.Peers.Where(c => c.SessionId == message.SessionId);
                    if (bySessionID.Count() > 0)
                        infNode = bySessionID.First() as RemoteMachine.Node;
                }
            }
            else
            {
                infNode = node; // it is about the hub
            }

            if (infNode == null)
                return false;

            lock (infNode)
            {
                try
                {
                    foreach (ProtocolParameter param in message.Parameters.Where(c => !string.IsNullOrEmpty(c.ParameterCode)))
                    {
                        switch (param.ParameterCode)
                        {
                            case "ID":
                                infNode.Identity.ClientId = param.ParameterValue;
                                break;
                            case "PD":
                                infNode.Identity.PrivateId = param.ParameterValue;
                                break;
                            case "I4":
                                System.Net.IPAddress ipHolder = null;
                                bool validIPAddress = System.Net.IPAddress.TryParse(param.ParameterValue, out ipHolder);
                                if (validIPAddress)
                                    infNode.Identity.IPv4Address = ipHolder;
                                break;
                            case "I6":
                                break;
                            case "U4":
                                infNode.Identity.Udp4Port = Convert.ToInt32(param.ParameterValue);
                                break;
                            case "U6":
                                break;
                            case "SS":
                                infNode.Identity.ShareSize = Convert.ToInt64(param.ParameterValue);
                                break;
                            case "SF":
                                infNode.Identity.SharedFilesCount = Convert.ToInt32(param.ParameterValue);
                                break;
                            case "VE":
                                infNode.Identity.ClientVersion = param.ParameterValue;
                                break;
                            case "US":
                                break;
                            case "DS":
                                break;
                            case "SL":
                                infNode.Identity.UploadSlots = Convert.ToInt32(param.ParameterValue);
                                break;
                            case "AS":
                                break;
                            case "AM":
                                break;
                            case "EM":
                                infNode.Identity.Email = param.ParameterValue;
                                break;
                            case "NI":
                                infNode.Identity.Nickname = param.ParameterValue;
                                break;
                            case "DE":
                                infNode.Identity.Description = param.ParameterValue;
                                break;
                            case "HN":
                                infNode.Identity.HubNormalCount = Convert.ToInt32(param.ParameterValue);
                                break;
                            case "HR":
                                infNode.Identity.HubRegisteredCount = Convert.ToInt32(param.ParameterValue);
                                break;
                            case "HO":
                                infNode.Identity.HubOperatorCount = Convert.ToInt32(param.ParameterValue);
                                break;
                            case "TO":
                                infNode.Identity.Token = param.ParameterValue;
                                break;
                            case "CT":
                                infNode.Identity.NodeType = (IdentityNodeTypes)Convert.ToInt32(param.ParameterValue);
                                break;
                            case "AW":
                                node.Identity.AwayStatus = param.ParameterValue != string.Empty ? ((IdentityAwayStatus)Convert.ToInt32(param.ParameterValue)) : IdentityAwayStatus.Online;
                                break;
                            case "SU":
                                foreach (string feat in param.ParameterValue.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
                                {

                                }
                                break;
                            case "RF":
                                break;
                            default:
                                break;
                        }
                    }
                }
                catch (FormatException ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex.Message);
                    Console.WriteLine(ex.Message);
                }
                catch (Exception e)
                {
                    System.Diagnostics.Debug.WriteLine(e.Message);
                }

                if (notHubIdentity)
                {
                    client.Peers.SetItem(infNode.Identity.ClientId, infNode as RemoteMachine.Peer);
                }
                else
                {
                    node = infNode;
                    ClientInterface.Peers.HubNameChanged(node.Identity.Nickname);
                }
            }
            if (message.MessageType == ProtocolMessage.MessageTypeEnum.Client)
            {
                if (node.Connection.State == RemoteMachine.ConnectionState.Identify)
                {
                    ProtocolMessageFactory factory = new Factory.FactoryINF();

                    Dictionary<int, object> factoryParams = new Dictionary<int, object>();
                    factoryParams.Add((int)Factory.FactoryINF.ParameterEnum.ClientID, client.Identity.ClientId);
                    factoryParams.Add((int)Factory.FactoryINF.ParameterEnum.Token, node.Identity.Token);
                    // Send Client Information to other Client
                    (node as RemoteMachine.Peer).Connection.Send(factory.Create(ProtocolMessage.MessageTypeEnum.Client, factoryParams).ToString());
                    node.Connection.State = RemoteMachine.ConnectionState.Normal;
                    Managers.PeerManager.PeerConnectionHandshakeComplete(node as RemoteMachine.Peer);
                }
                else if (node.Connection.State == RemoteMachine.ConnectionState.Normal)
                {
                    Managers.PeerManager.PeerConnectionHandshakeComplete(node as RemoteMachine.Peer);
                }
            }
            if (message.MessageType == ProtocolMessage.MessageTypeEnum.Broadcast && node.Connection.State != RemoteMachine.ConnectionState.Normal)
            {
                node.Connection.State = Netfraction.RemoteMachine.ConnectionState.Normal;

            }

            return true;
        }

        #endregion
    }
}
